package com.google.code.netcheckin.aprs;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;

import com.google.code.netcheckin.io.Ascii;
import com.google.code.netcheckin.util.ByteUtils;

/**
  * A class to hold a latitude or longitude for use in APRS packets.
  * @author Dick Repasky
  *
*/

public class LatOrLong {

	protected int	degrees;
	protected double	minutes;
			/** The number of digits to blot out with spaces in the APRS lat/long
			    (D)DDMM.MM. */
	protected int	ambiguity;
	protected byte	direction;

	public LatOrLong(byte[] data) throws UnsupportedEncodingException, ParseException {
		this(data, 0);
	}

	
	public LatOrLong(byte[] data, int offset) throws UnsupportedEncodingException, ParseException {
		int diroffset = indexOfDirection(data, offset);
		if (diroffset < 0) {
			throw new ParseException("no NSEW in lat/long", offset);
		}
		direction = data[diroffset];
		int deglen = 2;
		if (isLongitude()) {
			deglen = 3;
		}
		offset = diroffset - deglen - 5;
		degrees = Integer.parseInt(new String(data, offset, deglen));
		ambiguity = getAmbiguity(data, offset, offset + 6 + deglen, isLongitude());
		byte[] minbytes = ByteUtils.subArray(data, (offset + deglen), 5);
		final int ceil = minbytes.length;
		for (int idx = 0; idx < ceil; idx ++) {
			if (minbytes[idx] == Ascii.V_SP) {
				minbytes[idx] = Ascii.V_0;
			}
		}
		minutes = Double.parseDouble(new String(minbytes));
	}

	public LatOrLong(int degrees, double minutes, byte direction) {
		this(degrees, minutes, direction, 0);
	}

	public LatOrLong(int degrees, double minutes, byte direction, 
						int ambiguity) {
		this.degrees = degrees;
		this.minutes = minutes;
		this.direction = direction;
		this.ambiguity = ambiguity;
	}

	public LatOrLong(double decdeg, byte direction) {
		setDecimalDegrees(decdeg, direction);
		
	}

	public LatOrLong(double decdeg, byte direction, int ambiguity) {
		this(decdeg, direction);
		this.ambiguity = ambiguity;
	}

		/** Parse a string that provided a latitude or longitude in
		  * decimal degrees (e.g., "147.21987E"). No attempt is made to
		  * parse or determing ambiguity.
		  */

	public LatOrLong(String decimaldegrees) throws ParseException {
		decimaldegrees = decimaldegrees.trim();
		int diridx = indexOfDirection(decimaldegrees);
		if (diridx < 0) {
			throw new ParseException(decimaldegrees, decimaldegrees.length());
		}
		double dubldecdeg = Double.parseDouble(decimaldegrees.substring(0, diridx));
		setDecimalDegrees(dubldecdeg, (byte)decimaldegrees.charAt(diridx));
	}

	public int getAmbiguity() {
		return ambiguity;
	}

	public double getDecimalDeg() {
		return (double)degrees + minutes / 60.;
	}

	public byte getDirection() {
		return direction;
	}

	public boolean isLatitude() {
		return direction == Ascii.V_N || direction == Ascii.V_S;
	}

	public boolean isLongitude() {
		return direction == Ascii.V_W || direction == Ascii.V_E;
	}

	public void setAmbiguity(int ambiguity) {
		this.ambiguity = ambiguity;
	}

	public void setDecimalDegrees(double decdeg, byte direction) {
		degrees = new Double(decdeg).intValue();
		minutes = (decdeg % 1.0) * 60.0;
		this.direction = direction;
	}

	public byte[] toPacketBytes() {
		return toPacketBytes(new AprsFormatter());
	}

		/**
		  * If you provide a formatter, you can avoid instantiating
		  * a formatter for each call to toPacketBytes.
		  *
		*/

	public byte[] toPacketBytes(AprsFormatter formatter) {
		final int answerlen = isLatitude() ? 8 : 9;
		byte[] answer = new byte[answerlen];
		String temp = null;
		if (isLatitude()) {
			temp = formatter.formatDoubleZero(degrees);
		} else {
			temp = formatter.formatTripleZero(degrees);
		}
		int offset = ByteUtils.copyInto(answer, 0, temp.getBytes());
		offset = ByteUtils.copyInto(answer, offset, 
			formatter.formatCoordinateMinutes(minutes).getBytes());
		answer[offset] = direction;
		offset ++;
		imposeAmbiguity(answer, ambiguity);
		return answer;
	}

		/**
		  * Calculate ambiguity in an APRS latitude or longitude byte array.
		  * @param offset marks the beginning of the latitude or longitude in the byte array.
		  * @param end marks the end of the latitude or longitude in the byte array, and
		  * should be the byte following the N, S, E or W indicator.
		  * @param islong indicates whether the lat or long is a longitude
		  *
		*/

	public static int getAmbiguity(byte[] latorlong, int offset, int end, boolean islong) {
		int spat = ByteUtils.indexOfIn(new byte[] { Ascii.V_SP }, latorlong, offset);
		int deglen = islong ? 3 : 2;
		int answer = 0;
		if (spat > -1 && spat < end) {
			answer = 5 + deglen - (spat - offset);
			if (answer > 2) {
				answer --;
			}
		}
		return answer;
	}

		/** Impose ambiguity on a lat or long packet bytes.
		    the byte array sould contain only a latitude or longitude
		    (including direction) and be no longer than the data. */
	public static void imposeAmbiguity(byte[] latorlong, int ambiguity) {
		int idx = latorlong.length - 2;
		while(ambiguity > 0) {
			if (latorlong[idx] != Ascii.V_PERIOD) {
				latorlong[idx] = Ascii.V_SPACE;
				ambiguity --;
			}
			idx --;
		}
	}

		/** Return index of N, S, E, or W in string. */

	public static int indexOfDirection(String latorlong) {
		int answer = latorlong.indexOf('N');
		if (answer < 0) answer = latorlong.indexOf('S');
		if (answer < 0) answer = latorlong.indexOf('E');
		if (answer < 0) answer = latorlong.indexOf('W');
		return answer;
	}

	public static int indexOfDirection(final byte[] bytes) {
		return indexOfDirection(bytes, 0);
	}

	public static int indexOfDirection(final byte[] bytes, final int offset) {
		final int ceil = bytes.length;
		for (int idx = offset; idx < ceil; idx ++) {
			if (bytes[idx] == Ascii.V_N
			   || bytes[idx] == Ascii.V_W
			   || bytes[idx] == Ascii.V_S 
			   || bytes[idx] == Ascii.V_E) return idx;
		}
		return -1;
        }

}
